package com.chaos.auth.config;

import com.chaos.auth.api.dto.userdetails.AdminUserDetails;
import com.chaos.auth.api.dto.userdetails.UserUserDetails;
import com.chaos.auth.config.converter.UserAuthenticationConverter;
import com.chaos.auth.config.enums.AuthenticationIdentityEnums;
import com.chaos.auth.config.granter.AdminTokenGranter;
import com.chaos.auth.config.granter.UserTokenGranter;
import com.chaos.auth.service.auth.BaseClientDetailsService;
import com.chaos.framework.model.constant.Constants;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import javax.sql.DataSource;
import java.security.KeyPair;
import java.util.*;

/**
 * 认证服务配置
 *
 * @author Jacky
 */
@Slf4j
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private DataSource dataSource;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private BaseClientDetailsService clientDetailsService;

    @Autowired
    private UserAuthenticationConverter userAuthenticationConverter;

    @Value("${keystore.path}")
    private String keystorePath;

    @Value("${keystore.password}")
    private String keystorePassword;

    @Value("${keystore.alias}")
    private String keystoreAlias;


    @Override
    @SneakyThrows
    public void configure(ClientDetailsServiceConfigurer clients) {
        clients.withClientDetails(clientDetailsService);
    }

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
        tokenEnhancers.add(tokenEnhancer());
        tokenEnhancers.add(jwtAccessTokenConverter());
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
        // 不提供授权码模式、客户端模式、简化模式
        // 获取原有默认授权模式(授权码模式、密码模式、客户端模式、简化模式)的授权者
        List<TokenGranter> granterList = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));

        //管理员手机号一键登录
        granterList.add(
                new AdminTokenGranter(
                        endpoints.getTokenServices(),
                        endpoints.getClientDetailsService(),
                        endpoints.getOAuth2RequestFactory(),
                        authenticationManager,
                        AuthenticationIdentityEnums.LogInIdentity.ADMIN_PHONE.getCode()
                ));
        //管理员用户名登录
        granterList.add(
                new AdminTokenGranter(
                        endpoints.getTokenServices(),
                        endpoints.getClientDetailsService(),
                        endpoints.getOAuth2RequestFactory(),
                        authenticationManager,
                        AuthenticationIdentityEnums.LogInIdentity.ADMIN_USERNAME.getCode()
                ));

        // 手机号一键登录（C 端）
        granterList.add(
                new UserTokenGranter(
                        endpoints.getTokenServices(),
                        endpoints.getClientDetailsService(),
                        endpoints.getOAuth2RequestFactory(),
                        authenticationManager,
                        AuthenticationIdentityEnums.LogInIdentity.USER_PHONE.getCode()
                ));
        // 微信登录（C 端）
        granterList.add(
                new UserTokenGranter(
                        endpoints.getTokenServices(),
                        endpoints.getClientDetailsService(),
                        endpoints.getOAuth2RequestFactory(),
                        authenticationManager,
                        AuthenticationIdentityEnums.LogInIdentity.USER_WXOPENID.getCode()

                ));
        // 支付宝登录（C 端）
        granterList.add(
                new UserTokenGranter(
                        endpoints.getTokenServices(),
                        endpoints.getClientDetailsService(),
                        endpoints.getOAuth2RequestFactory(),
                        authenticationManager,
                        AuthenticationIdentityEnums.LogInIdentity.USER_ALIOPENID.getCode()
                ));

        CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);

        endpoints
                .authenticationManager(authenticationManager)
                .accessTokenConverter(jwtAccessTokenConverter())
                .tokenEnhancer(tokenEnhancerChain)
                .tokenGranter(compositeTokenGranter)
                .exceptionTranslator(loggingExceptionTranslator())
                .tokenServices(tokenServices(endpoints));
    }

    @Bean
    public TokenEnhancer tokenEnhancer() {
        return (accessToken, authentication) -> {
            Map<String, Object> additionalInfo = new HashMap<>();
            if (authentication.getUserAuthentication() == null) {
                return accessToken;
            }
            Object principal = authentication.getUserAuthentication().getPrincipal();

            //待扩展  OAuth2Request oAuth2Request = authentication.getOAuth2Request();
            if (principal instanceof UserUserDetails) {
                UserUserDetails userUserDetails = (UserUserDetails) principal;
                additionalInfo.put(Constants.USER_ID, userUserDetails.getUserId());
                additionalInfo.put("username", userUserDetails.getUsername());
                additionalInfo.put("authenticationIdentity", userUserDetails.getAuthenticationIdentity());
                additionalInfo.put("birthday", userUserDetails.getBirthday());
                additionalInfo.put("avatar", userUserDetails.getAvatar());
                additionalInfo.put("gender", userUserDetails.getGender());
                additionalInfo.put("phone", userUserDetails.getPhone());
                additionalInfo.put("tenantId", userUserDetails.getTenantId());
            } else if (principal instanceof AdminUserDetails) {
                AdminUserDetails adminUserDetails = (AdminUserDetails) principal;
                additionalInfo.put("adminId", adminUserDetails.getAdminId());
                additionalInfo.put("phone", adminUserDetails.getPhone());
                additionalInfo.put("username", adminUserDetails.getUsername());
                additionalInfo.put("tenantId", adminUserDetails.getTenantId());
            }

            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);

            return accessToken;
        };
    }

    public DefaultTokenServices tokenServices(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
        tokenEnhancers.add(tokenEnhancer());
        tokenEnhancers.add(jwtAccessTokenConverter());
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);

        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(clientDetailsService);
        tokenServices.setTokenEnhancer(tokenEnhancerChain);

        /** refresh_token有两种使用方式：重复使用(true)、非重复使用(false)，默认为true
         *  1 重复使用：access_token过期刷新时， refresh_token过期时间未改变，仍以初次生成的时间为准
         *  2 非重复使用：access_token过期刷新时， refresh_token过期时间延续，在refresh_token有效期内刷新便永不失效达到无需再次登录的目的
         */
        tokenServices.setReuseRefreshToken(false);
        return tokenServices;

    }

    /**
     * 使用非对称加密算法对token签名
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        ((DefaultAccessTokenConverter) converter.getAccessTokenConverter()).setUserTokenConverter(userAuthenticationConverter);
        converter.setKeyPair(keyPair());
        return converter;
    }

    @Bean
    public KeyPair keyPair() {
        Resource resource;
        if (keystorePath.startsWith("classpath:")) {
            resource = new ClassPathResource(keystorePath.substring("classpath:".length()));
        } else {
            resource = new FileSystemResource(keystorePath);
        }
        KeyStoreKeyFactory factory = new KeyStoreKeyFactory(resource, keystorePassword.toCharArray());
        KeyPair keyPair = factory.getKeyPair(keystoreAlias, keystorePassword.toCharArray());
        return keyPair;
    }


    @Bean
    public WebResponseExceptionTranslator loggingExceptionTranslator() {
        return new DefaultWebResponseExceptionTranslator() {
            @Override
            public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
                e.printStackTrace();
                ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);
                HttpHeaders headers = new HttpHeaders();
                headers.setAll(responseEntity.getHeaders().toSingleValueMap());
                OAuth2Exception excBody = responseEntity.getBody();
                return new ResponseEntity<>(excBody, headers, responseEntity.getStatusCode());
            }
        };
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients()
        ;
    }


}
